use std::collections::HashSet;

type Coord = (i32, i32);

#[derive(Debug, Clone, Copy)]
enum Command {
    Left,
    Up,
    Right,
    Down,
}

fn parse_input() -> Vec<Command> {
    let input = include_str!("../input.txt");
    let mut commands = Vec::new();
    for line in input.lines() {
        let distance = line[2..].parse::<i32>().unwrap();
        let command =  match &line[0..1] {
            "L" => Command::Left,
            "U" => Command::Up,
            "R" => Command::Right,
            "D" => Command::Down,
            _ => panic!("Not L/U/R/D"),
        };
        for _ in 0..distance {
            commands.push(command);
        }
    }
    commands
}

fn adjust_coords(coord: Coord) -> [Coord; 8] {
    let (x, y) = coord;
    [
        (x - 1, y + 1), (x    , y + 1), (x + 1, y + 1),
        (x - 1, y    ),                 (x + 1, y    ),
        (x - 1, y - 1), (x    , y - 1), (x + 1, y - 1) 
    ]
}

fn is_tail_need_move(head_coord: Coord, tail_coord: Coord) -> bool {
    head_coord != tail_coord && 
    adjust_coords(tail_coord).into_iter().all(|coord| -> bool {
        head_coord != coord
    })
}

fn coords_distance(coord1: Coord, coord2: Coord) -> i32 {
    (coord1.0 - coord2.0).abs() + (coord1.1 - coord2.1).abs()
}

fn next_tail_coord(head_coord: Coord, tail_coord: Coord) -> Coord {
    if !is_tail_need_move(head_coord, tail_coord) {
        tail_coord
    } else {
        adjust_coords(tail_coord)
            .into_iter()
            .map(|coord| -> (Coord, i32) {
                (coord, coords_distance(coord, head_coord))
            })
            .min_by_key(|(_, dis)| dis.clone())
            .unwrap()
            .0
    }
}

fn part1() {
    let commands = parse_input();
    let mut coords: HashSet<Coord> = HashSet::new();
    let (mut head_coord, mut tail_coord) = ((0, 0), (0, 0));
    coords.insert(tail_coord);

    for command in commands {
        head_coord = match command {
            Command::Left  => (head_coord.0 - 1, head_coord.1),
            Command::Up    => (head_coord.0, head_coord.1 + 1),
            Command::Right => (head_coord.0 + 1, head_coord.1),
            Command::Down  => (head_coord.0, head_coord.1 - 1),
        };

        tail_coord = next_tail_coord(head_coord, tail_coord);

        coords.insert(tail_coord);
    }

    println!("Part 1: {}", coords.len());
}

fn part2() {
    let commands = parse_input();
    let mut coord_set: HashSet<Coord> = HashSet::new();
    let mut coords: [Coord; 10] = [(0, 0); 10];
    coord_set.insert(coords[9]);

    for command in commands {
        coords[0] = match command {
            Command::Left  => (coords[0].0 - 1, coords[0].1),
            Command::Up    => (coords[0].0, coords[0].1 + 1),
            Command::Right => (coords[0].0 + 1, coords[0].1),
            Command::Down  => (coords[0].0, coords[0].1 - 1),
        };

        for i in 1..10 {
            coords[i] = next_tail_coord(coords[i - 1], coords[i]);
        }

        coord_set.insert(coords[9]);
    }

    println!("Part 2: {}", coord_set.len());
}

fn main() {
    part1();
    part2();
}
